fix(perps-controller): correct significant-figure counting for sub-$1 HyperLiquid prices - #10052
Open
gomesalexandre wants to merge 2 commits into
Open
Conversation
… HyperLiquid prices countSignificantFigures only stripped leading zeros from the integer part of a number, so every price under $1 (integer part '0') had its leading fractional zeros counted as significant figures. 0.001234 (4 real significant figures, HyperLiquid's own canonical valid-price example) was miscounted as 6, which fed into roundToSignificantFigures and needlessly re-rounded it down to 0.00123, destroying real precision. The same package's getPriceTick (orderCalculations.ts) already implements HyperLiquid's precision rule correctly via order-of-magnitude (log10), and computeChaseQuotePrice routes its correctly-computed tick through the broken formatHyperLiquidPrice -> countSignificantFigures path, producing a real functional failure: a post-only chase order meant to rest one tick inside the spread came back at or across the touch instead, for any sub-$1 book. Fix: count significant figures by stripping leading zeros across the combined integer+decimal digit string (matching the true definition), and reconcile roundToSignificantFigures's rounding with getPriceTick's magnitude-based approach instead of maintaining two independently-wrong rounding strategies. Also fixes the existing significantFigures.test.ts assertion that pinned 0.001234's significant-figure count as 6 (captured from the buggy implementation, not derived) - it should be 4.
gomesalexandre
marked this pull request as ready for review
September 1, 2026 17:19
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Explanation
countSignificantFigures(packages/perps-controller/src/utils/significantFigures.ts) only stripped leading zeros from the integer part of a number, then added the full decimal-part length. For any HyperLiquid perp price under $1, the integer part is always"0", so its two/three leading fractional zeros got counted as significant figures too:0.001234— HyperLiquid's own canonical example of a valid perp price (4 significant figures) — was miscounted as 6. That fed intoroundToSignificantFigures, whoseallowedDecimalDigits = maxSigFigs - integerSigFigsdegenerates into a fixed-decimal-places round whenintegerSigFigsis 0, needlessly re-rounding an already-valid price down to0.00123and destroying a real digit of precision.The package already implements the correct rule elsewhere, and the two disagree.
getPriceTick(orderCalculations.ts:330-347) computes HyperLiquid's precision rule correctly viaMath.floor(Math.log10(price))— magnitude-based, so it doesn't care whether the price is above or below 1.computeChaseQuotePricecallsgetPriceTickfor the correct tick size, then routes the resulting price through the brokenformatHyperLiquidPrice→countSignificantFigurespath. For a sub-$1 book this is a real functional failure, not just a display nit:Fix
countSignificantFigures: strip leading zeros from the combined integer+decimal digit string, not just the integer part.roundToSignificantFigures: switched from the ad-hocmaxSigFigs - integerSigFigsdecimal budget to the same order-of-magnitude approachgetPriceTickalready uses correctly, so the package has one significant-figures rule instead of two independently-maintained (and disagreeing) copies.significantFigures.test.ts's existing assertion pinning'0.001234'at 6 significant figures — that expectation was captured from the buggy implementation, not derived; the correct value is 4.< 1branch acrosscountSignificantFigures,hasExceededSignificantFigures,roundToSignificantFigures, andformatHyperLiquidPrice(which had zero unit tests before this PR), plus thecomputeChaseQuotePrice/getPriceTicksub-$1 regression above.Receipts
Genuine red-before/green-after — reverted just the source fix and reran the new tests against the old logic:
Restored the fix — same tests, plus the full package suite:
Risk / scope
Low. Two pure functions and their one downstream consumer (
formatHyperLiquidPrice); no change togetPriceTickitself, which was already correct and is now the reference the other function follows.Known limitation, unaffected by this PR —
countSignificantFigures/roundToSignificantFiguresare exported as general-purpose pure functions, and neither the old nor the new implementation correctly parses a raw exponential-notation string (e.g."1.23e-7") if called directly with one — both mishandle it identically. This is unreachable via the actual price-formatting pipeline:formatHyperLiquidPricebounds its input throughtoFixed(maxDecimalPlaces)(max 6 decimal places) before these functions ever see it, and(0.000001).toString()stays in decimal notation — JS only switches to exponential below1e-6. Not fixing full scientific-notation support here since it's pre-existing, unregressed, and outside the price-formatting domain this package actually exercises.Codex reviewed the diff adversarially but didn't return a verdict inside a reasonable time budget (~9 minutes of visible, genuine progress — not a stall); killed the process (targeting only its own PID) and substituted the two specific things it was mid-investigation on myself: the exponential-notation question above, and whether
toFixed's IEEE-754 rounding behavior (e.g.(0.123455).toFixed(5)rounding down due to0.123455not being exactly representable) is a new regression — it isn't;toFixedwas already used by the pre-fix code and this is an inherent floating-point property, not something this diff introduces.Note
Low Risk
Pure math utilities in perps-controller with no auth or persistence changes; main effect is more accurate HyperLiquid price strings for sub-$1 assets and chase orders.
Overview
Fixes HyperLiquid price formatting for sub-$1 markets by correcting how significant figures are counted and rounded in
significantFigures.ts.countSignificantFiguresnow strips leading zeros from the full digit string (integer + fraction), so values like0.001234count as 4 sig figs instead of 6.roundToSignificantFiguresno longer uses the broken integer-part decimal budget; it uses the same order-of-magnitude rule asgetPriceTick, and returns prices unchanged when they already fit within the cap.That stops
formatHyperLiquidPricefrom over-rounding valid limit/TP/SL/trigger prices and fixes post-only chase quotes on penny books: chase prices stay one tick inside the spread instead of resting at or through the touch (ALO rejections). Changelog and regression tests coverformatHyperLiquidPrice, chase ladder behavior, and the sig-fig helpers.Reviewed by Cursor Bugbot for commit 7547eff. Bugbot is set up for automated code reviews on this repo. Configure here.